spiro

W. Joel Schneider

2018-09-22

Installation

If you do not have the remotes package, install it from CRAN like so:

install.packages("remotes")

Install spiro from GitHub like so:

remotes::install_github("wjschne/spiro")

Why spiro?

As a kid, I loved spirographs. I still do. Software to make digital spirographs is not hard to find, but I had several reasons to make my own softward using R:

Enter the spiro package. It can do things like this:


# Cairo::Cairo(file = "spiro.png", width = 1000, height = 1000, pointsize = 12, type = "png", dpi = 300, bg = "black")

spiro(
  fixed_radius = 800,
  cycling_radius = 677,
  pen_radius = 100,
  color_groups = 10,
  color_cycles = 61,
  windings = 677 * 0.5,
  transparency = 1,
  start_angle = 0,
  points_per_polygon = 50,
  colors = scico::scico(n = 10, palette = "cork"),
  end_at_beginning = F,
  draw_fills = F,
  background_color = NA 
)

Or this:

library(spiro)
spiro(fixed_radius = 800, 
           cycling_radius = 751, 
           pen_radius = 40,
           color_groups = 4,
           color_cycles = 2,
           points_per_polygon = 20000,
           colors = c("midnightblue","white","purple4","white"),
           end_at_beginning = F,
           background_color = "black")

Or this:

spiro(
  fixed_radius = 359,
  cycling_radius = 261,
  pen_radius = 40,
  color_groups = 36,
  color_cycles = 36,
  draw_fills = F,
  points_per_polygon = 200,
  line_width = 3.5,
  background_color = "black",
  colors = c(
    scales::div_gradient_pal(low = "royalblue4",
                             mid = "black",
                             high = "firebrick4")(seq(0, 1, length.out = 18)),
    scales::div_gradient_pal(low = "royalblue",
                             mid = "white",
                             high = "firebrick")(seq(0, 1, length.out = 18))
  )
)

What is a spirograph?

A spirograph is made with a pen attached to a circle that rolls around another cyrcle:

Circle radii

The basic parameters to play with on a spirograph are the radius of the fixed circle, the radius of the moving circle, and the pen placement radius inside the moving circle. To make the spirograph from the animation, the radius of the fixed circle is 3, the radius of the cycling circle is 1, and the radius of the pen placement is 2. Technically, this is a hypotrochoid because the moving circle is inside the fixed circle.

An epitrochoid is made with the moving circle outside the fixed circle.

To make an epitrochoid, set the cycling_radius parameter to a negative value:

Fills vs. Lines

If you want just lines instead of fills, set draw_fills to FALSE:

Using color

Color groups

The figure can be split into multiple parts. Setting the color_groups parameter to 2, splits the figure in half:

Color palettes

By default, the colors are selected from the viridis pallete via the scales package. If you want to select different colors, you can do so directly with a vector of colors:

You can use any color palette as well. Here we use the rainbow palette:

A great list of color palettes and functions can be found in Emil Hvitfeldt’s paletteer package.

Color recycling

You can recycle the colors as many times as you wish. Here are 12 colors recycled 3 times (i.e., color_cycles = 3). Thus, there are 3 × 12 = 36 segments in this spirograph.

Recycling colors can dramatically change the look of the figure. Here is a single cycle of 5 colors:

Here is the spirograph with the same 5 colors recycled 200 times:

Notice that the points_per_polygon was set to a lower value in the example above. By default, each recycled group has 1000 points each time the figure winds around the fixed circle. By default, the number of windings is equal to the cycling radius. In this example, that would mean 5 color groups × 200 color cycles &times 100 windings, × 1000 points = 100 million points. By setting points_per_polygon to 10, there are now “only” 5 color groups × 200 color cycles × 50 points = 50,000 points. This means that the file size will be much smaller and the image quality is not noticeably reduced.

Using with ggplot2

The spiro function returns raw data, and the .svg image creation can be turned off by setting savefile to FALSE. Thus, it is easy to use the data with ggplot2 or any other plotting system you wish to use.

library(ggplot2)
library(ggpolypath)
d <- spiro(fixed_radius = 37, 
      cycling_radius = 12 ,
      pen_radius = 31, 
      color_groups = 6, 
      color_cycles = 6, 
      points_per_polygon = 1000,
      background_color = "black", 
      savefile = FALSE)

#> # A tibble: 36 x 2
#>    x                    col      
#>    <list>               <chr>    
#>  1 <tibble [1,001 x 4]> #440154FF
#>  2 <tibble [1,001 x 4]> #414487FF
#>  3 <tibble [1,001 x 4]> #2A788EFF
#>  4 <tibble [1,001 x 4]> #22A884FF
#>  5 <tibble [1,001 x 4]> #7AD151FF
#>  6 <tibble [1,001 x 4]> #FDE725FF
#>  7 <tibble [1,001 x 4]> #440154FF
#>  8 <tibble [1,001 x 4]> #414487FF
#>  9 <tibble [1,001 x 4]> #2A788EFF
#> 10 <tibble [1,001 x 4]> #22A884FF
#> # ... with 26 more rows
d$color_id <- factor(d$color_id)
d$color_cycle_id <- factor(d$color_cycle_id)

ggplot(d, aes(x,y)) + 
  geom_path(aes(color = color_cycle_id), lwd = 1) +
  theme_void() +
  theme(legend.position = "none") +
  coord_equal()

Merging images

Let’s merge two .svg images starting with this image:

s1 <- spiro(fixed_radius = 3,
      cycling_radius = 1,
      colors = "royalblue",
      transparency = 1,
      filename = "spiroblue.svg")

Now let’s make the same image but rotated 60 degrees and in a different color:

s2 <- spiro(fixed_radius = 3,
      cycling_radius = 1, 
      rotation = 60 / 180 * pi,
      colors = "firebrick",
      transparency = 1,
      filename = "spirored.svg",
      origin_x = -1.5)

Now let’s merge the images:

merge_image(input_files = c(s1,s2),output_file = "redblue.svg")

Animate Your Spirographs

Rotations

To rotate an svg image, we need to start with a static image, which we will call Rotate1.svg.

The rotate_image function rotates the image around its center by default:

By default, the output of the spiro function is the file name. Thus, we can use the pipe operator %>% from the magrittr package to string commands together. By default, the rotate_image function overwrites the input file.

Here we rotate an image around a point not at the center. Imagine the image is a square of size 1. The top left corner is point (0,0) and the bottom right cornder is point (1,1). The center is point (0.5,0.5). Here we rotate the image around the point (0.25,0.38), which centered horizontally but a little above the vertical center. I have added a red dot to show the point around which the image is rotating.